package com.zusmart.core.condition.support;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import com.zusmart.base.util.AnnotationUtils;
import com.zusmart.base.util.Assert;
import com.zusmart.base.util.ReflectionUtils;
import com.zusmart.core.bean.BeanDefinition;
import com.zusmart.core.bean.BeanDefinitionRegistry;
import com.zusmart.core.condition.ConditionContext;
import com.zusmart.core.condition.ConditionFrom;
import com.zusmart.core.condition.ConditionManager;
import com.zusmart.core.condition.ConditionType;
import com.zusmart.core.condition.Conditional;
import com.zusmart.core.config.ConfigSourceManager;
import com.zusmart.core.plugin.PluginManager;

public abstract class AbstractConditionManager implements ConditionManager {

	private final Set<ConditionContext> contexts = new LinkedHashSet<ConditionContext>();
	private final BeanDefinitionRegistry beanDefinitionRegistry;

	protected AbstractConditionManager(BeanDefinitionRegistry beanDefinitionRegistry) {
		Assert.isNull(beanDefinitionRegistry, "bean definition registry must not be null");
		this.beanDefinitionRegistry = beanDefinitionRegistry;
	}

	@Override
	public BeanDefinitionRegistry getBeanDefinitionRegistry() {
		return this.beanDefinitionRegistry;
	}

	@Override
	public void attachBeanDefinition(BeanDefinition beanDefinition) {
		if (null == beanDefinition) {
			return;
		}
		this.resolveBeanDefinition(beanDefinition);
	}

	@Override
	public void detachBeanDefinition(BeanDefinition beanDefinition) {
		if (null == beanDefinition) {
			return;
		}
		for (ConditionContext conditionContext : this.contexts) {
			if (conditionContext.getSourceBeanDefinition().equals(beanDefinition)) {
				this.contexts.remove(conditionContext);
			}
		}
	}

	@Override
	public boolean execute(BeanDefinition targetBeanDefinition, PluginManager pluginManager, ConfigSourceManager configSourceManager) {
		Map<String, BeanDefinition> beanDefinitions = this.beanDefinitionRegistry.getBeanDefinitionMappings();
		if(null == this.contexts || this.contexts.size() == 0){
			return true;
		}
		for (ConditionContext context : this.contexts) {
			if (targetBeanDefinition == context.getSourceBeanDefinition() && context.execute(beanDefinitions, targetBeanDefinition, pluginManager, configSourceManager) == false) {
				return false;
			}
		}
		return true;
	}

	protected void resolveBeanDefinition(BeanDefinition beanDefinition) {
		Class<?> beanType = beanDefinition.getBeanClass();
		Conditional conditional = AnnotationUtils.getAnnotation(beanType, Conditional.class);
		if (null != conditional) {
			ConditionContext conditionContext = this.createConditionContext(beanDefinition, conditional, null, ConditionFrom.Class, ConditionType.Native);
			this.contexts.add(conditionContext);
		}
		Annotation[] annotations = beanType.getAnnotations();
		if (null != annotations && annotations.length > 0) {
			for (Annotation annotation : annotations) {
				Conditional c = AnnotationUtils.getAnnotation(annotation.annotationType(), Conditional.class);
				if (null != c) {
					ConditionContext conditionContext = this.createConditionContext(beanDefinition, c, annotation, ConditionFrom.Class, ConditionType.Extend);
					this.contexts.add(conditionContext);
				}
			}
		}
		Method[] methods = ReflectionUtils.getDeclaredMethods(beanType);
		if (null != methods && methods.length > 0) {
			for (Method method : methods) {
				Conditional c = AnnotationUtils.getAnnotation(method, Conditional.class);
				if (null != c) {
					ConditionContext conditionContext = this.createConditionContext(beanDefinition, c, null, ConditionFrom.Method, ConditionType.Native);
					this.contexts.add(conditionContext);
				}
				Annotation[] ans = method.getAnnotations();
				if (null != ans && ans.length > 0) {
					for (Annotation an : ans) {
						Conditional cc = AnnotationUtils.getAnnotation(an.annotationType(), Conditional.class);
						if (null != cc) {
							ConditionContext conditionContext = this.createConditionContext(beanDefinition, cc, an, ConditionFrom.Method, ConditionType.Extend);
							this.contexts.add(conditionContext);
						}
					}
				}
			}
		}
	}

	protected abstract ConditionContext createConditionContext(BeanDefinition beanDefinition, Conditional conditional, Annotation annotation, ConditionFrom conditionFrom, ConditionType conditionType);

}